package com.itheima.mp.controller;


import cn.hutool.core.bean.BeanUtil;
import com.itheima.mp.domain.dto.UserFormDTO;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.domain.query.UserQuery;
import com.itheima.mp.domain.vo.UserVO;
import com.itheima.mp.service.IUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RequestMapping("/users")
// 定义请求路径前缀为/users
@RestController
// 标注为Spring MVC的控制器，并且返回JSON格式数据
@Api(tags = "用户管理接口")
// 使用Swagger注解，定义API文档的标签
@RequiredArgsConstructor
/*
1. Lombok 的 @RequiredArgsConstructor 注解
简介：@RequiredArgsConstructor 是 Lombok 提供的一个注解，用于自动生成包含所有 final 字段和标记为 @NonNull 字段的构造函数。
作用：简化构造函数的编写，避免手动编写构造函数，减少样板代码。
2. 使用 @RequiredArgsConstructor 的好处
简化代码：自动生成构造函数，减少手动编写的代码量。
强制依赖注入：通过构造函数注入依赖，确保依赖的不可变性和非空性。
提高可读性：明确类的依赖关系，使代码更易于理解和维护。
4. 生成的构造函数
使用 @RequiredArgsConstructor 注解后，Lombok 会在编译时生成如下构造函数：
public UserController(IUserService userService) {
    this.userService = userService;
}
这个构造函数会将 userService 作为参数，并在构造函数内部完成字段的初始化。
5. 依赖注入
构造函数注入：通过构造函数注入依赖，确保依赖在对象创建时就被注入，避免了依赖未初始化的问题。
Spring 框架：在 Spring 框架中，使用 @RequiredArgsConstructor 可以与 Spring 的依赖注入机制很好地结合，自动注入所需的依赖。
6. Lombok 依赖
要使用 Lombok 的 @RequiredArgsConstructor 注解，需要在项目中添加 Lombok 依赖：*/
public class UserController {

    private final IUserService userService;
    /*
    定义IUserService类型的final字段，用于依赖注入
    构造函数的功能
    1.初始化 final 字段：
    构造函数确保所有 final 字段在对象创建时被正确初始化。
    例如，userService 和 serviceName 字段在对象创建时被赋值。
    2.确保依赖注入：
    通过构造函数注入依赖，确保依赖关系在对象创建时就已经确定，避免了未初始化的依赖。
    例如，IUserService 类型的 userService 字段在对象创建时被注入。
    3.提高代码的可读性和安全性：
    使用 @RequiredArgsConstructor 注解自动生成构造函数，减少手动编写构造函数的样板代码，提高代码的可读性。
    通过构造函数注入，确保依赖关系的不可变性，增加代码的安全性和稳定性。
     */

    /*依赖注入（Dependency Injection, DI）是什么？
    依赖注入是一种设计模式，用于将对象之间的依赖关系从代码中解耦出来，并将它们集中管理。通过依赖注入，类不再直接创建其依赖对象，而是通过外部传递这些依赖，从而实现松耦合。

    依赖注入的原理
    依赖注入的核心思想是将对象的创建和依赖关系的管理交给外部容器（如 Spring 容器）来处理。容器根据配置文件或注解，自动创建和管理对象，并在需要时将依赖对象注入到目标对象中。

    依赖注入的作用
    1.降低耦合度：
    依赖注入将对象的创建和依赖关系的管理交给外部容器（如 Spring 容器），从而降低类之间的耦合度，使代码更易于维护和扩展。
    2.提高代码的可测试性：
    通过依赖注入，可以轻松地替换依赖对象，从而方便进行单元测试。例如，可以在测试时注入模拟对象（Mock），而不是实际的依赖对象。
    3.增强灵活性：
    依赖注入允许在运行时动态地改变依赖关系，而无需修改代码。这使得应用程序更加灵活，可以根据不同的配置或环境进行调整。
    4.促进职责分离：
    依赖注入鼓励将对象的创建和使用分离开来，使得每个类只关注自己的职责，符合单一职责原则（SRP）。


    依赖注入的实现方式
    1.构造函数注入：
    通过构造函数传递依赖对象。

    2.Setter 方法注入：
    通过 Setter 方法传递依赖对象。

    3.字段注入：
    直接在字段上使用注解（如 @Autowired）注入依赖对象。
    */

    /*
    1. DTO (Data Transfer Object)
    定义：数据传输对象，用于在不同层之间传输数据。
    用途：主要用于将数据从一个系统或层传输到另一个系统或层，通常用于远程调用或网络传输。
    特点：DTO 通常只包含数据字段，不包含业务逻辑。
    2. VO (Value Object)
    定义：值对象，用于封装一组相关的数据，通常用于表示层。
    用途：主要用于展示层，将页面或组件的所有数据封装起来，可能包含多个 DTO 对象。
    特点：VO 对象的字段可以是只读的，一旦创建就不能修改。
    3. PO (Persistent Object)
    定义：持久化对象，与数据库表结构一一对应。
    用途：主要用于数据访问层，与数据库进行交互，保存和读取数据。
    特点：PO 对象包含与数据库表中的列相对应的字段，并且通常具有与数据库操作相关的方法。
    */

    @PostMapping
    // 定义POST请求的映射路径
    @ApiOperation("新增用户接口")
    // 使用Swagger注解，描述API操作
    public void saveUser(@RequestBody  UserFormDTO userDTO){

        // 1.把DTO拷贝到po
        User user = BeanUtil.copyProperties(userDTO, User.class);
        // 使用BeanUtil工具类，将DTO对象的属性拷贝到PO对象
        // BeanUtil.copyProperties 方法
        //功能：用于将一个对象的属性值复制到另一个对象中。它通过反射机制获取源对象和目标对象的属性，并将源对象的属性值赋给目标对象。
        //参数：source：源对象，这里是 userDTO。    targetClass：目标类，这里是 User.class，表示要创建一个 User 类的新实例。

        // 2.新增
        userService.save(user);
        // 调用userService的save方法，保存用户信息

    }




    @DeleteMapping("{id}")
    // 定义DELETE请求的映射路径，路径变量为id
    @ApiOperation("删除用户接口")
    // 使用Swagger注解，描述API操作
    public void deleteUserById(@ApiParam("用户id") @PathVariable("id") Long id){


        userService.removeById(id);
        // 调用userService的removeById方法，删除指定id的用户

    }




    @GetMapping("{id}")
    // 定义GET请求的映射路径，路径变量为id
    @ApiOperation("根据id查询用户接口")
    // 使用Swagger注解，描述API操作
    public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Long id){

        /*
        // 1.查询用户PO
        User user = userService.getById(id);
        // 调用userService的getById方法，查询指定id的用户

        // 2.把PO拷贝到VO
        return BeanUtil.copyProperties(user,UserVO.class);
        // 使用BeanUtil工具类，将PO对象的属性拷贝到VO对象
        */


        return userService.queryUserAndAddressById(id);

    }





    @GetMapping
    @ApiOperation("根据ids批量查询用户接口")
    // 使用Swagger注解，描述API操作
    public List<UserVO> queryUserByIds(@ApiParam("用户id集合") @RequestParam("ids") List<Long> ids){

        /*
        // 1.查询用户PO
        List<User> users = userService.listByIds(ids);
        // 调用userService的listByIds方法，查询指定id集合的用户

        // 2.把PO拷贝到VO
        return BeanUtil.copyToList(users,UserVO.class);
        // 使用BeanUtil工具类，将PO对象列表的属性拷贝到VO对象列表
        */

        return userService.queryUserAndAddressByIds(ids);

    }






    @PutMapping("/{id}/deduction/{money}")
    @ApiOperation("扣减用户余额接口")
    // 使用Swagger注解，描述API操作
    public void deductMoneyById(
            @ApiParam("用户id") @PathVariable("id") Long id,
            @ApiParam("扣减的金额") @PathVariable("money") Integer money){

        userService.deductBalance(id,money);
        // 调用userService的removeById方法，删除指定id的用户

    }

    /*
    总结：在什么情况下，怎么使用MP中提供的Service和Mapper中的方法？
    对于一些简单的方法，可以直接在controller里调用MP提供的Service方法，无需任何自定义Service或Mapper。
    当业务逻辑相对复杂，需要自己写一些业务，而MP只提供基本的增删改查，没有业务，此时需要自定义Service方法，并且在其中编写业务逻辑。
    当BaseMapper或Service提供的方法不足以满足增删改查的需求时，需要编写mapper，自定义SQL语句。
    */





    @GetMapping("/list")
    @ApiOperation("根据复杂条件查询用户接口")
    // 使用Swagger注解，描述API操作
    public List<UserVO> queryUser(UserQuery query){

        // 1.查询用户PO
        List<User> users = userService.queryUsers(
                query.getName(),query.getStatus(),query.getMinBalance(),query.getMaxBalance());
        // 调用userService的listByIds方法，查询指定id集合的用户

        // 2.把PO拷贝到VO
        return BeanUtil.copyToList(users,UserVO.class);
        // 使用BeanUtil工具类，将PO对象列表的属性拷贝到VO对象列表

    }





}
